package edu.purdue.cs.aggr.messageprocessor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import edu.purdue.cs.aggr.AggrException;
import edu.purdue.cs.aggr.Controller;
import edu.purdue.cs.aggr.Controller.VertexData;
import edu.purdue.cs.aggr.Logger;
import edu.purdue.cs.aggr.Node;
import edu.purdue.cs.aggr.Vertex;
import edu.purdue.cs.aggr.VertexManager;
import edu.purdue.cs.aggr.message.EventNotificationMessage;
import edu.purdue.cs.aggr.message.EventNotificationMessage.Event;
import edu.purdue.cs.aggr.message.EventMessageBuilder;
import edu.purdue.cs.aggr.message.Message;

public class EventNotifiationMessageProcessor extends MessageProcessor {

	public static Map<String, Long> leafProcessingTimes = new HashMap<String, Long>();
  public static Map<String, Long> updateLeafProcessingTimes = new HashMap<String, Long>();
	public static Map<String, Long> leafStartedAggregating = new HashMap<String, Long>();
	public static long aggregateStartTime = -1;
  public static long updateAggregateStartTime = -1;
	public static long rootDoneProcessing = -1;
  public static long updateRootDoneProcessing = -1;
	private Node node;
	
	public EventNotifiationMessageProcessor (Node node) {
		this.node = node;
	}
	
	@Override
	public void processMessage(Message message) throws AggrException {
	  Logger.log(node, "Processing event notification message from:" + Controller.getIndexOf(message.getSender()));
	  
	  EventNotificationMessage eventMsg = (EventNotificationMessage) message;
	  Event event = eventMsg.getEvent();
	  
	  if (event == Event.LEAF_DONE_PROCESSING) {
	    Logger.log(node, "Got LEAF_DONE_PROCESSING event...");
//	    leafStartedAggregating.put(eventMsg.getSenderId(),System.currentTimeMillis());
	    leafProcessingTimes.put(eventMsg.getSender(), eventMsg.getTime());
	    if (leafProcessingTimes.size() == Controller.leaves.size()) {
	    	//send start command to all leaves
	    	for (VertexData data : Controller.leaves) {
	    	  node.getSender().sendMessage(EventMessageBuilder.createEventMessage(node, Event.AGGREGATE_COMMAND, data.getId(), null), data.getId());
	    	}
	    	aggregateStartTime = System.currentTimeMillis();
	    }
	  } else if (event == Event.UPDATE_DONE_PROCESSING) {
      updateLeafProcessingTimes.put(eventMsg.getSender(), eventMsg.getTime());
	    Logger.log(node, "Got UPDATE_DONE_PROCESSING event... updatesExpected: " + Controller.updatesExpected + " procTimesSize:" + updateLeafProcessingTimes.size());
      if (updateLeafProcessingTimes.size() == Controller.updatesExpected) {
        //send start command to all leaves
        for (String data : updateLeafProcessingTimes.keySet()) {
          node.getSender().sendMessage(EventMessageBuilder.createEventMessage(node, Event.AGGREGATE_COMMAND, data, null), ((Controller)node).getLocation(data));
        }
        updateAggregateStartTime = System.currentTimeMillis();
      }
    } else if (event == Event.BEGINNING_UPDATE) {
      Logger.log(node, "BEGINNING_UPDATE received");
      Vertex me = ((VertexManager) node).getVertex(message.getDestination());
      me.incrementExpectedUpdates(message.getSender());
      if(me.getDestinationVertex() != null) {
        ((VertexManager) node).sendMessage(EventMessageBuilder.createEventMessage(me, Event.BEGINNING_UPDATE, me.getDestinationVertex(), null));
        System.out.println("BEGINNING_UPDATE forwarded");
      }
    } else if (event == Event.ROOT_DONE) {
      Logger.log(node, "Got ROOT_DONE event...");
		  if (rootDoneProcessing == -1) {
  	    rootDoneProcessing = System.currentTimeMillis();
  	    printResults(leafProcessingTimes, aggregateStartTime, rootDoneProcessing, "Initial: ");
		  } else {
		    updateRootDoneProcessing = System.currentTimeMillis();
        printResults(updateLeafProcessingTimes, updateAggregateStartTime, updateRootDoneProcessing, "Update: ");
		  }
	  } else if (event == Event.AGGREGATE_COMMAND) {
	    Logger.log(node, "Got AGGREGATE_COMMAND event... destination: " + message.getDestination());
		  ((VertexManager) node).getVertex(message.getDestination()).sendStoredAccumulatorToDestination();
	  } else if (event == Event.VERTEX_RECEIVED) {
      Logger.log(node, "Got VERTEX_RECEIVED event");
	    Controller.combinesReceived++;
	    if (Controller.combinesReceived == Controller.combinesExpected) {
	      Logger.log(node, "Combine phase finished: " + (System.currentTimeMillis() - Controller.combineStarted));
	    }
	  }
	}

	private void printResults(Map<String, Long> processingTimes, long start, long end, String label) {
//		System.out.println("Printing results ...");
		
		long leafProcessingTotal = 0;
		int leafProcessingCount = 0;
		for (Long l : processingTimes.values()) {
//			System.out.println("Leaf procesing time :" + l);
			leafProcessingTotal += l; 
			leafProcessingCount++;
		}
		
		long leafProcessingAverage = leafProcessingTotal / leafProcessingCount;
		System.out.println(label + "Average Processing Time :" + leafProcessingAverage + " ms");
		
//		long leafStartedAggregatingTotal = 0;
//		int leafStartedAggregatingCount = 0;
//		for (Long l : leafStartedAggregating.values()) {
//			leafStartedAggregatingTotal += l;
//			leafStartedAggregatingCount++;
////			System.out.println("Non-averaged aggregation time :" + (rootDoneProcessing - l));
//		}
		
//		long leafStartedAggregatingAverage = leafStartedAggregatingTotal / leafStartedAggregatingCount;
		System.out.println(label + "Average Aggregation Time :" + (end - start) + " ms");
	}

}
